package com.ballyza;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Stack;

import com.ballyza.gals.SemanticError;
import com.ballyza.gals.Token;

public class SemanticAction {

	private Map<String, String> tokens;
	private Map<String, String> identificadoresTipos;
	private List<String> identificadores;
	private Stack<String> stack;
	private StringBuilder codigo;
	private String nomeArquivo;
	
	private String operador;
	private int endereco = 0;
	private String tipo_atual;
	private String identificador_atual;
	
	private Stack<String> rotulo_if;
	private Stack<String> rotulo_loop;
	private Stack<String> rotulo_exit;
	private String operadorRelacional;
	
	private Map<String, String> tipos;
	private Map<String, String> classes;
	private Map<String, String> operadorCodigo;
	
	public SemanticAction(String nomeArquivo) {
		tokens = new HashMap<String, String>();
		identificadoresTipos = new HashMap<String, String>();
		identificadores = new ArrayList<String>();
		codigo = new StringBuilder();
		stack = new Stack<String>();
		rotulo_if = new Stack<String>();
		rotulo_loop = new Stack<String>();
		rotulo_exit = new Stack<String>();
		this.nomeArquivo = nomeArquivo;
		
		tipos = new HashMap<String, String>();
		tipos.put("int", "int64");
		tipos.put("float", "float64");
		tipos.put("date", "data_dele");
		tipos.put("time", "time_dele");
		tipos.put("boolean", "bool");
		tipos.put("string", "string");
		
		operadorCodigo = new HashMap<String, String>();
		operadorCodigo.put("==", "ceq".concat(newLine()));
		operadorCodigo.put(">", "cgt".concat(newLine()));
		operadorCodigo.put("<", "clt".concat(newLine()));
		operadorCodigo.put(">=", "clt".concat(newLine()).concat(" ldc.i8 1 ").concat(newLine()).concat(" xor").concat(newLine()));
		operadorCodigo.put("<=", "cgt".concat(newLine()).concat(" ldc.i8 1 ").concat(newLine()).concat(" xor").concat(newLine()));
		operadorCodigo.put("!=", "ceq".concat(newLine()).concat(" ldc.i8 1 ").concat(newLine()).concat(" xor").concat(newLine()));
		
		classes = new HashMap<String, String>();
		classes.put("int64", "Int64");
		classes.put("float64", "Double");
		classes.put("boolean", "Boolean");
	}

	public void executeAction1(Token token) {
		codigo.append(".assembly extern mscorlib{}");
		codigo.append(newLine());
		codigo.append(".assembly ");
		codigo.append(nomeArquivo);
		codigo.append(" {}");
		codigo.append(newLine());
	}
	
	public void executeAction2(Token token) {
		String nome = tokens.get("nome_programa");
		if(nome.equals(token.getLexeme())) {
			codigo.append("     ret").append(newLine());
            codigo.append("  }").append(newLine());
            codigo.append("}").append(newLine());
		} else {
			throw new SemanticError("Nome do programa deve ser o mesmo do início!", token.getPosition());
		}
	}
	
	public void executeAction3(Token token) {
		String tipo = identificadoresTipos.get(token.getLexeme());
		tipo = tipos.get(tipo);
		codigo.append(" call void [mscorlib]System.Console::Write(").append(tipo).append(")");
		codigo.append(newLine());
	}
	
	public void executeAction4(Token token) {
		codigo.append(" or ").append(newLine());
	}
	
	public void executeAction5(Token token) {
		codigo.append(" and ").append(newLine());
	}
	
	public void executeAction6(Token token) {
		tipo_atual = "boolean";
		codigo.append(" ldc.i8 1 ").append(newLine());
	}
	
	public void executeAction7(Token token) {
		tipo_atual = "boolean";
		codigo.append(" ldc.i8 0 ").append(newLine());
	}
	
	public void executeAction8(Token token) {
		
		codigo.append(" ldc.i8.1 ").append(newLine()).append(" xor ").append(newLine());
	}
	
	public void executeAction9(Token token) {
		operadorRelacional = token.getLexeme();
	}
	
	public void executeAction10(Token token) {
		/*String operadorCodigo = this.operadorCodigo.get(token.getLexeme());
		codigo.append(operadorCodigo);*/
		String operador = operadorCodigo.get(operadorRelacional);
		codigo.append(operador);
	}
	
	public void executeAction11(Token token) {
		codigo.append(" add ").append(newLine());
	}
	
	public void executeAction12(Token token) {
		codigo.append(" sub ").append(newLine());
	}
	 
	public void executeAction13(Token token) {
		codigo.append(" mul ").append(newLine());
	}
	
	public void executeAction14(Token token) {
		//codigo.append(" div.un").append(newLine());
		codigo.append(" div.un ").append(newLine());
	}
	
	public void executeAction15(Token token) {
		codigo.append(" div ").append(newLine());
	}
	
	public void executeAction16(Token token) {
		codigo.append(" rem ").append(newLine());
	}
	
	public void executeAction17(Token token) {
		tipo_atual = "int";
		//identificadores.add(token.getLexeme());
		//identificadoresTipos.put(token.getLexeme(), "int64 ");
		codigo.append(" ldc.i8 ").append(token.getLexeme()).append(newLine());
	}
	
	public void executeAction18(Token token) {
		tipo_atual = "float";
		//identificadores.add(token.getLexeme());
		//identificadoresTipos.put(token.getLexeme(), "float64 ");
		
		codigo.append(" ldc.r8 ").append(token.getLexeme()).append(newLine());
	}
	
	public void executeAction19(Token token) {
		// constante_data
	}
	
	public void executeAction20(Token token) {
		// constante_hora
	}
	
	public void executeAction21(Token token) {
		tipo_atual = "string";
		codigo.append(" ldstr ").append(token.getLexeme()).append(newLine());
	}
	
	public void executeAction22(Token token) {
		codigo.append(" ldc.i8.s -1 ").append(newLine());
		codigo.append(" mul ").append(newLine());
	}
	public void executeAction23(Token token) {
		identificadores.add(token.getLexeme());
	}
	
	public void executeAction24(Token token) {
		if (identificadoresTipos.get(token.getLexeme()) == null){
			throw new SemanticError("Tipo do identificador não declarado", token.getPosition());
		}
		tipo_atual = "identificador";
		identificador_atual = token.getLexeme();
		codigo.append(" ldloc ").append(token.getLexeme()).append(newLine());
	}
	
	public void executeAction25(Token token) {
		StringBuilder variaveis = new StringBuilder();
		
		int qtde = 0;
		for(String variavel : identificadores) {
			if(tokens.containsKey(variavel)) {
				throw new SemanticError("Identificador já declarado", variavel, token.getPosition());
			} else {
				tokens.put(variavel, token.getLexeme());
				identificadoresTipos.put(variavel, token.getLexeme());
				qtde++;
				if(qtde > 1) {
					variaveis.append(", ");
				}
				variaveis.append(tipos.get(token.getLexeme())).append(" ").append(variavel);
			}
		}
		codigo.append(" .locals( ").append(variaveis.toString()).append(" ) ").append(newLine());
		identificadores.clear();
	}
	
	public void executeAction26(Token token) {
		String tipo;
		
		for (String identificador : identificadores){
			tipo = identificadoresTipos.get(identificador);
			if (tipo.equals(tipo_atual) && tipo_atual != "identificador"){
				codigo.append(" stloc ").append(identificador).append(newLine());
			}
			else{
				throw new SemanticError("Variavel " + identificador + " não é do tipo " + tipo_atual, token.getPosition());
			}
		}
		codigo.append(" dup ").append(newLine());
		identificadores.clear();
	}
	
	public void executeAction27(Token token) {
		String endereco_exit = rotulo_exit.peek();
		codigo.append(" br ").append(endereco_exit).append(newLine());
	}
	
	public void executeAction28(Token token) {
		// FIXME: VERIFICAR PQ NAO RETORNA O TOKEN '+='
		operador = token.getLexeme();
		
		String identificador = identificadores.get(0);
		
		if(operador.equals("+=")) {
			codigo.append(" ldloc ").append(identificador).append(newLine());
		} else if(operador.equals("-=")) {
			codigo.append(" ldloc ").append(identificador).append(newLine());
		}
	}
	
	public void executeAction29(Token token) {
		String identificador = identificadores.get(0);
		String tipoidentificador = identificadoresTipos.get(identificador);
		if (tipoidentificador == null){
			throw new SemanticError("Tipo do identificador não declarado", token.getPosition());
		}
		
		if(operador.equals("+=")) {
			codigo.append(" add ").append(newLine());
		} else if(operador.equals("-=")) {
			codigo.append(" sub ").append(newLine());
		}
		
		String tipoid = identificadoresTipos.get(identificador_atual);
		if (tipoidentificador.equals(tipo_atual) || tipoidentificador.equals(tipoid)){
			codigo.append(" stloc ").append(identificador).append(newLine());
		} else {
			throw new SemanticError("variável "+ identificador + " não é equivalente ao tipo " + tipo_atual, token.getPosition());
		}		
		identificadores.clear();
	}
	
	public void executeAction30(Token token) {
		for (String identificador : identificadores) {
			if(!tokens.containsKey(identificador)) {
				throw new SemanticError("Identificador não declarado", identificador, token.getPosition());
			} else {
				String tipo = identificadoresTipos.get(identificador);
				tipo = tipos.get(tipo);
				String classe = classes.get(tipo);
				codigo.append(" call string [mscorlib]System.Console::ReadLine()").append(newLine());
				codigo.append(" call " + tipo + " [mscorlib]System." + classe + "::Parse(string)").append(newLine());
				codigo.append(" stloc ").append(identificador).append(newLine());
			}
		}
		
		identificadores.clear();
		
		/*String var = token.getLexeme();
		
		String tipo = identificadoresTipos.get(var);
		tipo = tipos.get(tipo);
		String classe = classes.get(tipo);
		codigo.append("call string [mscorlib]System.Console::ReadLine()").append(newLine());
		codigo.append("call " + tipo + " [mscorlib]System." + classe + "::Parse(string)").append(newLine());*/
	}
	
	public void executeAction31(Token token) {
		String endereco = getAddress("a0");
		codigo.append(" brfalse ").append(endereco).append(newLine());
		rotulo_if.push(endereco);	
	}
	
	public void executeAction32(Token token) {
		String endereco_fim = rotulo_if.pop();
		codigo.append(endereco_fim).append(":").append(newLine());
	}
	
	public void executeAction33(Token token) {
		String endereco_fim = getAddress("b0");
		String endereco_else = rotulo_if.pop();
		rotulo_if.push(endereco_fim);
		codigo.append(" br ").append(endereco_fim).append(newLine()).append(endereco_else).append(":").append(newLine());
	}
	
	public void executeAction34(Token token) {
		String endereco = getAddress("c");
		rotulo_loop.push(endereco);
		String endereco_fim = getAddress("c");
		rotulo_exit.push(endereco_fim);
		codigo.append(endereco).append(":").append(newLine());
	}
	
	public void executeAction35(Token token) {
		String endereco_loop = rotulo_loop.pop();
		String endereco_exit = rotulo_exit.pop(); 
		codigo.append(" br ").append(endereco_loop).append(newLine());
		codigo.append(endereco_exit).append(":").append(newLine());
	}
	
	public void executeAction36(Token token) {
		tokens.put("nome_programa", token.getLexeme());
		codigo.append(".module ").append(nomeArquivo).append(".exe");
		codigo.append(newLine());
		codigo.append(".class public ").append(token.getLexeme());
		codigo.append(" ").append("{").append(newLine());
		codigo.append(".method static public void principal()");
		codigo.append(newLine()).append("{");
		codigo.append(".entrypoint").append(newLine());
	}
	
	private String newLine() {
		return "\n";
	}
	
	public StringBuilder getCodigo() {
		return codigo;
	}
	
	public String getAddress(String prefixo){
		endereco++;
		return prefixo+endereco;
	}

	public void setCodigo(StringBuilder codigo) {
		this.codigo = codigo;
	}
	
	
}